#include "config.h"
#include "configgroup.h"
#include "variant.h"

#include <iostream>
#include <fstream>

using namespace std;

Config::Config(string path, string perm) : m_filePath(path)
{
  ios_base::openmode mode;
  if(perm == "r")
  {
    mode = ios_base::in;
    m_writeable = false;
  }
  else
  {
    mode = ios_base::in | ios_base::out;
    m_writeable = true;
  }
  m_file.open(m_filePath.c_str(), mode);

  reparseConfiguration();
}

Config::Config() : m_filePath(""),m_writeable(false)
{
}

Config::~Config()
{
  m_file.close();

  map<string, ConfigGroup*>::iterator it = m_groups.begin();
  for(; it != m_groups.end(); it++)
  {
    delete it->second;
  }
}

string Config::path()
{
  return m_filePath;
}

void Config::setPath(string path)
{
  if(m_file.is_open())
    m_file.close();

  ios_base::openmode mode = ios_base::in | ios_base::out | ios_base::trunc;
  m_writeable = true;
  m_filePath=path;
}

bool Config::sync()
{
  if(!m_writeable)
    return false;

  if(m_file.is_open())
    m_file.close();

  m_file.open(m_filePath.c_str(), ios_base::out | ios_base::trunc);
  if(m_file.fail())
  {
    return false;
    //exit(1);
  }

  // Let the config groups write themselves to disc
  map<string, ConfigGroup*>::iterator it = m_groups.begin();
  for(; it != m_groups.end(); it++)
  {
    it->second->sync(m_file);
  }

  m_file.sync();
  m_file.close();

  m_file.open(m_filePath.c_str(), ios_base::out | ios_base::in);

  return true;
}

bool Config::hasGroup(const string& name)
{
  map<string, ConfigGroup*>::iterator it =  m_groups.find(name);
  if(it == m_groups.end())
    return false;
  else
    return it->second->exists();
}

list<string> Config::groupList()
{
  list<string> retList;
  map<string, ConfigGroup*>::iterator it = m_groups.begin();
  for(; it != m_groups.end(); it++)
  {
    retList.push_back(it->first);
  }

  return retList;
}


void Config::deleteGroup(const string& name)
{
  map<string, ConfigGroup*>::iterator it =  m_groups.find(name);
  if(it == m_groups.end() || !it->second->exists())
    return;
  else
  {
    delete it->second;
    m_groups.erase(it);
  }
}

ConfigGroup& Config::operator[](const string& groupName)
{
  map<string, ConfigGroup*>::iterator it =  m_groups.find(groupName);
  if(it == m_groups.end())
  {
    ConfigGroup* cg = new ConfigGroup(*this, groupName);
    cg->m_exists = false;
    m_groups[groupName] = cg;
    return *cg;
  }
  else
    return *m_groups[groupName];
}


void Config::reparseConfiguration()
{
  string line;
  ConfigGroup* currentGroup = 0;
  int state = 0;
  string::size_type pos = 0, posEnd = 0;

  //     m_file.seekg (0, ios::beg);

  while(getline(m_file, line))
  {
    line = trimLine(line);

    // Looking for a group header
    if((state == 0 || state == 1) && !line.empty() && line[0] == '[')
    {
      pos = line.find( '[', 0 );

      posEnd = line.find( ']', pos + 1 );
      posEnd = (posEnd < line.size()) ?  posEnd : line.size();

      //Failed
      if(posEnd == line.size())
        continue;

      state = 1;

      string s = line.substr(pos + 1, posEnd - pos - 1);

      currentGroup = &(*this)[s];
      currentGroup->m_exists = true;

      pos = line.find( '[', posEnd + 1 );
      pos = (pos < line.size()) ?  pos : line.size();

      //Find the inner-most group
      while(pos < line.size())
      {
        posEnd = line.find( ']', pos + 1 );
        posEnd = (posEnd < line.size()) ?  posEnd : line.size();

        // Failed
        if(posEnd == line.size())
        {
          state = 0;
          break;
        }

        s = line.substr(pos + 1, posEnd - pos - 1);
        currentGroup = &(*currentGroup)[s];
        currentGroup->m_exists = true;

        pos = line.find( '[', posEnd + 1 );
        pos = (pos < line.size()) ?  pos : line.size();
      }

    }
    // We are inside a group, looking for an entry
    else if(state == 1 && !line.empty() && (line[0] >= 'A' || line[0] <= 'z'))
    {
      string key, value;

      posEnd = line.find( '=', 1 );
      posEnd = (posEnd < line.size()) ?  posEnd : line.size();

      // Failed
      if(posEnd == line.size())
        continue;

      key = line.substr(0, posEnd);
      key = trimLine(key, false); // don't strip comments

      value = line.substr(posEnd + 1, line.size());
      value = trimLine(value, false); // don't strip comments

      // By default all entries are stored as strings
      Variant* variant = new Variant(value);
      variant->setTokenized(true);
      currentGroup->m_entries[key] = variant;
    }
  }
}

void Config::setWriteable(const bool option)
{
  m_writeable=option;
}

bool Config::isWriteable()
{
  return m_writeable;
}

string Config::trimLine(const string& line, bool stripComments)
{
  string ret(line);

  // Remove comments
  if(stripComments)
  {
    string::size_type pos = ret.find('#');
    if(pos < ret.size())
      ret = ret.substr(0, pos);
  }

  // Trim Both leading and trailing spaces
  string::size_type startpos = ret.find_first_not_of(" \t");
  string::size_type endpos = ret.find_last_not_of(" \t");

  // if all spaces or empty return an empty string  
  if(startpos < ret.size() && endpos < ret.size())
    ret = ret.substr( startpos, endpos-startpos+1 );
  else
    ret = "";

  return ret;
}
